#include "list.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

extern void * my_malloc(size_t size);
extern void my_free(void *data);

static int node_add(clist_t *list, void *data)
{
    if (list == NULL || data == NULL) {
        return CLIST_ERR_NULL_PTR;
    }

    node_t *node = (node_t *)my_malloc(sizeof(node_t));
    memset(node, 0, sizeof(node_t));

    node->data = data;
    node->next = NULL;

    if (list->len == 0) {
        list->head = node;
        list->tail = node;
    } else {
        list->tail->next = node;
        list->tail = node;
    }

    list->len++;
    return 0;
}

static void *node_remove(clist_t *list)
{
    if (list == NULL) {
        return NULL;
    }

    void *data = NULL;
    node_t *node;
    node_t *last;

    if (list->len == 0) {
        return NULL;
    } else if (list->len == 1) {
        last = list->head;
        list->head = NULL;
        list->tail = NULL;
    } else {
        node = list->head;
        last = list->tail;
        while ((node = node->next) != NULL) {
            if (node->next == list->tail) {
                list->tail = node;
                list->tail->next = NULL;
                break;
            }
        }
    }
    data = last->data;
    my_free(last);
    list->len--;
    return data;
}

static void * node_remove_by_data(clist_t *list, void *data, bool (*traverse)(void *node_data, void *data))
{
    if (list == NULL) {
        return NULL;
    }

    node_t *node = NULL;
    void *ret_data = NULL;

    if (list->len == 0) {
        return NULL;
    } else if (list->len == 1) {
        node = list->head;
        if (traverse(list->head->data, data) == true) {
            list->head = NULL;
            list->tail = NULL;
            ret_data = node->data;
            my_free(node);
            list->len--;
        }
    } else {
        node = list->head;
        if (traverse(list->head->data, data) == true) {
            list->head = node->next;
            ret_data = node->data;
            my_free(node);
            list->len--;
        } else {
            node_t *pre_node = list->head;
            node = pre_node;
            while ((node = node->next) != NULL) {
                if (traverse(node->data, data) == true) {
                    if (node->next == NULL) {
                        list->tail = pre_node;
                    }
                    pre_node->next = node->next;
                    ret_data = node->data;
                    my_free(node);
                    list->len--;
                    break;
                }
                pre_node = pre_node->next;
            }
        }
    }
    return ret_data;
}

static int node_insert(clist_t *list, void *data, void *ref_data, bool (*traverse)(void *node_data, void *data), bool previous)
{
    if (list == NULL) {
        return CLIST_ERR_NULL_PTR;
    }

    node_t *pre_node = NULL;
    node_t *node = NULL;

    if (previous) {
        if (list->len == 0) {
            return CLIST_ERR_LIST_EMPTY;
        } else if (list->len >= 1) {
            node = list->head;
            if (traverse(list->head->data, ref_data) == true) {
                pre_node = my_malloc(sizeof(node_t));
                pre_node->data = data;
                pre_node->next = node;
                list->head = pre_node;
            } else {
                pre_node = list->head;
                node = pre_node;
                while ((node = node->next) != NULL) {
                    if (traverse(node->data, ref_data) == true) {
                        node_t *insert_node = my_malloc(sizeof(node_t));
                        insert_node->data = data;
                        insert_node->next = node;
                        pre_node->next = insert_node;
                        break;
                    }
                    pre_node = pre_node->next;
                }
            }
        }
    } else {
        if (list->len == 0) {
            return CLIST_ERR_LIST_EMPTY;
        } else {
            node = list->head;
            do {
                if (traverse(node->data, ref_data) == true) {
                    node_t *insert_node = my_malloc(sizeof(node_t));
                    insert_node->data = data;
                    insert_node->next = NULL;
                    node->next = insert_node;
                    if (node == list->tail) {
                        list->tail = insert_node;
                    }
                }
            }while((node = node->next) != NULL);
        }
    }

    list->len++;
    return 0;
}

void list_traverse(clist_t *list, void *data, void (*traverse)(void *node_data, void *data))
{
    if (list == NULL) {
        return ;
    }

    node_t *node = list->head;
    if (list->len >= 1) {
        traverse(list->head->data, data);
    }
    while (node && ((node = node->next) != NULL)) {
        traverse(node->data, data);
    }
}

void list_del(clist_t *list, void (*data_free)(void *data))
{
    if (list == NULL) {
        return ;
    }

    if (list->len == 0) {
        my_free(list);
        return ;
    }

    node_t *node = NULL;
    node_t *next_node = NULL;

    node = list->head;
    while (node) {
        next_node = node->next;
        if (data_free) {
            data_free(node->data);
        }
        my_free(node);
        node = next_node;
    }

    my_free(list);
}

clist_t * clist_new()
{
    clist_t *list = (clist_t *)my_malloc(sizeof(clist_t));
    if (list == NULL) {
        return NULL;
    }
    memset(list, 0, sizeof(clist_t));

    list->head = NULL;
    list->tail = NULL;
    list->len = 0;

    list->node_add = node_add;
    list->node_remove = node_remove;
    list->node_remove_by_data = node_remove_by_data;
    list->node_insert = node_insert;
    list->list_traverse = list_traverse;
    list->list_del = list_del;

    return list;
}

void clist_del(clist_t *list, void (*data_free)(void *data))
{
    list_del(list, data_free);
}
